//-------------------------------------------------------------------------------------------------------
//      Copyright (c) 2005-2009, Be Nanashi-San 
// 
//      Permission is hereby granted, free of charge, to any person obtaining a 
//      copy of this software and associated documentation files (the "Software"), 
//      to deal in the Software without restriction, including without limitation 
//      the rights to use, copy, modify, merge, publish, distribute, sublicense, 
//      and/or sell copies of the Software, and to permit persons to whom the 
//      Software is furnished to do so, subject to the following conditions: 
// 
//      The above copyright notice and this permission notice shall be included in 
//      all copies or substantial portions of the Software. 
// 
//      THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
//      IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
//      FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
//      AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
//      LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
//      FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
//      DEALINGS IN THE SOFTWARE. 
// 
//      File Name:   	K2chTextView.cpp
//      Author:      	Be Nanashi-San@598 in "BeOS - Zeta R4" Thread 
//						(http://pc8.2ch.net/test/read.cgi/os/1120676818/598)
//		Description:	互換K2chTextView.
//						VIEW_LIMITERで表示するレス数を制限できる(待ち時間が我慢ならないようなら)
//						PostMessage()使いまくり。Zetaでは-D_ZETA_USING_DEPRECATED_API_が必要
//						DisplayThreadViewからとMStickerかも使われ、条件が多少違うので煩わしくなっている。
//-------------------------------------------------------------------------------------------------------
#include <Application.h>		// be_app
#include <Beep.h>				// beep
#include <MenuItem.h>
#include <Message.h>
#include <Messenger.h>
#include <PopUpMenu.h>
#include <Clipboard.h>
#include <ScrollView.h>
#include <File.h>
#include <Debug.h>
#include <ctype.h>
#include <kernel/OS.h>			// for thread
#include <stdio.h>

#include "messages.h"
#include "datattr.h"

#include "config.h"
#include "config_user.h"
#include "K2chTextView.h"
#include "K2chTipTextView.h"
#include "MSticker.h"
#include "NetUtil.h"
#include "Util.h"
#include "setting.h"
#include "TSettings.h"

////////////////////////////////////////////////////////////////////////////////
// Copy_to_Clipboard
//
//
////////////////////////////////////////////////////////////////////////////////
static void
Copy_to_Clipboard(const char *data)
{
	if (!be_clipboard->Lock()) return;
	
	be_clipboard->Clear();
	BMessage *clipper = be_clipboard->Data();
	// sizeとして文字列の終端(NULL文字)を入れてはならない
	// 入れるとBTextViewは続く一行を存在するが表示しないという奇妙な現象になる
	clipper->AddData("text/plain", B_MIME_TYPE, data, strlen(data), false);
	be_clipboard->Commit();
	be_clipboard->Unlock();
}
////////////////////////////////////////////////////////////////////////////////
// K2chTextView
//
//
////////////////////////////////////////////////////////////////////////////////
K2chTextView::K2chTextView(const char *name, uint32 flags)
	: Base2chTextView(name, flags/*|B_WILL_DRAW|B_FRAME_EVENTS|B_FULL_UPDATE_ON_RESIZE*/)
	, fAction(this)
	, fThread_id(0)
	, fCancel(false)
	, fTipStiker(NULL)
	, fCountTimer(5)
	, fScrollView(NULL)
{
	this->SetDispmode(DISP_PARTIAL_THREAD);
}
////////////////////////////////////////////////////////////////////////////////
// `K2chTextView
//
//
////////////////////////////////////////////////////////////////////////////////
K2chTextView::~K2chTextView()
{
//	delete fFile;
	if (fTipStiker) fTipStiker->Close();
}
////////////////////////////////////////////////////////////////////////////////
// KeyDown
//
//
////////////////////////////////////////////////////////////////////////////////
void
K2chTextView::KeyDown(const char* bytes, int32 /*numBytes*/)
{
	if (bytes[0] == B_UP_ARROW){
		return this->AutoScroll(false, -1);
	}else if (bytes[0] == B_DOWN_ARROW) {
		return this->AutoScroll(Bounds().Contains(PointAt(OffsetAt(TextLength()))), 1);
	}
}

////////////////////////////////////////////////////////////////////////////////
// AutoScroll
//
//
////////////////////////////////////////////////////////////////////////////////
void
K2chTextView::AutoScroll(bool last_in, int32 top_thread_no)
{
	top_thread_no += 1 + fCurPositon;
	int32 offset = BTextView::OffsetAt(Bounds().LeftTop());
	int32  count = fThread.size();
	for (int32 i = 1; i < count; i++) {
		if (offset < fThread[i]) break;
		top_thread_no++;
	}

	// auto RELOAD

	int32 abs_thread_no = top_thread_no - 1 - fCurPositon;
	if (abs_thread_no < 0) {
		if (0 < fCurPositon) {
			this->Seek(-fThreadDisplayRange, SEEK_CUR);
			this->SetDispmode(DISP_PARTIAL_THREAD);
			this->LoadText(fThread.size() + fThreadDisplayRange, top_thread_no);
		}
		return;
	}
	
	uint32 uabs_thread_no = abs_thread_no;
	if (last_in || fThread.size() <= uabs_thread_no) {
	 /* 最後がBounds()の中に入ったら次をリロードする */
		if (top_thread_no < fDat.CountItems()) {
			this->SetDispmode(DISP_PARTIAL_THREAD);
			this->LoadText(fThread.size() + fThreadDisplayRange, top_thread_no);
		}
	}else{
		this->ScrollToRes(top_thread_no);
	}
}
////////////////////////////////////////////////////////////////////////////////
// MessageReceived
//
//
////////////////////////////////////////////////////////////////////////////////
void
K2chTextView::MessageReceived(BMessage *msg)
{
	switch (msg->what) {
		case kMSG_FIND:
			this->FindString(msg->FindString("findthis"));
			break;
		case kMSG_BORN_BABY:
			this->ShowTipWindow(msg->FindInt32("thread_no"));
			break;
		case kMSG_THREAD_END:
			break;
		case kMSG_GOOGLE:
			this->GoogleSearch(msg->FindString("selected"));
			break;
		case kMSG_CHECK_LOCATION:
			Launcher(B_URL_HTTP, msg->FindString("text"));
			break;
		case kMSG_COPY_LOCATION:
		case kMSG_COPY_THREAD:
			Copy_to_Clipboard(msg->FindString("text"));
			break;
		case kMENU_THREAD_GO_TOP:
			this->AppendThread(0, SEEK_SET, DISP_PARTIAL_THREAD);
			break;
		case kMENU_THREAD_GO_LAST:
			this->AppendThread(-fThreadDisplayRange, SEEK_END, DISP_PARTIAL_THREAD);
			break;
		case kMENU_THREAD_GO_PREV:
			this->AppendThread(-fThreadDisplayRange, SEEK_CUR, DISP_PARTIAL_THREAD);
			break;
		case kMENU_THREAD_GO_NEXT:
			this->AppendThread(fThreadDisplayRange, SEEK_CUR, DISP_PARTIAL_THREAD);
			break;
		case kMENU_THREAD_SHOW_All:
			this->AppendThread(0, SEEK_SET, DISP_ALL_THREAD);
			break;
		default:
			Base2chTextView::MessageReceived(msg);
			break;
	}
}
////////////////////////////////////////////////////////////////////////////////
// AppendThread
//
// TODO;名称
////////////////////////////////////////////////////////////////////////////////
void
K2chTextView::AppendThread(int32 offset, uint32 seek_mode, disp_mode dmode)
{
	this->Seek(offset, seek_mode);
	this->SetDispmode(dmode);
	this->LoadText();
}

////////////////////////////////////////////////////////////////////////////////
// MouseDown
//
//
////////////////////////////////////////////////////////////////////////////////
void
K2chTextView::MouseDown(BPoint where)
{
	uint32 buttons = Window()->CurrentMessage()->FindInt32("buttons");
	if (buttons == B_SECONDARY_MOUSE_BUTTON) {
		BPopUpMenu menu("2ch+ Poper");
		this->AddMenuItems(&menu, where);
		menu.SetTargetForItems(this);
		menu.Go(ConvertToScreen(where) - BPoint(5, 10), true);
	} else {
		BTextView::MouseDown(where);
		action_info* action = fAction.ItemTo(OffsetAt(where));
		fCursorOffset = (action) ? action->offset : -1;
		fMousePoint = where;
	}
}
////////////////////////////////////////////////////////////////////////////////
// MouseUp
//
//
////////////////////////////////////////////////////////////////////////////////
void
K2chTextView::MouseUp(BPoint where)
{
	action_info* action = fAction.ItemTo(OffsetAt(where));
	if (action) {
		fCursorOffset = action->offset;
		fCountTimer = 5;
		this->Invoke();
	}else{
		fCursorOffset = -1;
	}
	
	BRect spot = BRect(fMousePoint, fMousePoint).InsetByCopy(-2, -2);
	if (spot.Contains(where))	/* Click(ほぼ同じ場所でDown、Up)か */
		this->SelectionInvoke();

	BTextView::MouseUp(where);
}
////////////////////////////////////////////////////////////////////////////////
// MouseMoved
//
//
////////////////////////////////////////////////////////////////////////////////
void
K2chTextView::MouseMoved(BPoint where, uint32 transit, const BMessage *msg)
{
	BTextView::MouseMoved(where, transit, msg);

	if (transit != B_INSIDE_VIEW) return;
	
	int32 offset = fCursorOffset;
	action_info* action = fAction.ItemTo(OffsetAt(where));
	fCursorOffset = (action) ? action->offset : -1;
	if (where != fStatic_where) {
		/* B_INSIDE_VIEWはMouseMoveしていなくても呼ばれている?ので */
		fCountTimer = 5;					/* reset */
		fStatic_where = where;
	}
	
	if (fCursorOffset == offset) return;
	if (!action) return;
	
	if(action->type == ACTION_AGE){
		this->PostStatus("age");
	}else if(action->type == ACTION_SAGE){
		this->PostStatus("sage");
	}else{
		this->PostStatus(action->name);
	}
}
////////////////////////////////////////////////////////////////////////////////
// Pulse
//
//
////////////////////////////////////////////////////////////////////////////////
void
K2chTextView::Pulse()
{
	if (!Window()->IsActive()) return;
	
	if (--fCountTimer == 0) {
		action_info* action = fAction.ItemTo(fCursorOffset);
		if (action && action->name && strncmp(action->name, "../", 3) == 0){
			this->Invoke();
		}else{
			this->SelectionInvoke();
		}
		fCountTimer = 5;
	}
}
////////////////////////////////////////////////////////////////////////////////
// FindString
// 文字列(find)を現在のセレクション位置の次からスキャンを初めて見つかればSelect()し
// ScrollToSelection()で見えるようスクロールする。
//
////////////////////////////////////////////////////////////////////////////////
void
K2chTextView::FindString(const char *find)
{
	int32 start, finish;
	GetSelection(&start, &finish);

	const size_t nchars = strlen(find);
	const char *text = this->Text();
	int32 length = this->TextLength();

	for (int32 i = finish + 1; i < length; i++) {
		if (strncasecmp(find, text + i, nchars) == 0) {
			BTextView::Select(i, i + nchars);
			this->ScrollToSelection();
			return;
		}
	}

	for (int32 i = 0; i < start; i++) {
		if (strncasecmp(find, text + i, nchars) == 0) {
			Select(i, i + nchars);
			this->ScrollToSelection();
			return;
		}
	}
	beep();
}
////////////////////////////////////////////////////////////////////////////////
// PostStatus
//
// 20051120:classに移動。WindowAt()からBiew::Window()へ変更。
////////////////////////////////////////////////////////////////////////////////
void
K2chTextView::PostStatus(const char *title)
{
	BMessage msg(kMSG_STATUS_MESSAGE);
	msg.AddString("message", title);
	BMessenger(this->Window()).SendMessage(&msg);
}
////////////////////////////////////////////////////////////////////////////////
// ScrollToRes
//
//
////////////////////////////////////////////////////////////////////////////////
void
K2chTextView::ScrollToRes(int32 thread_no)
{
	int32 abs_thread_no = thread_no - 1 - fCurPositon;
	if (abs_thread_no < 0) return;
	
	uint32 uabs_thread_no = abs_thread_no;
	if (uabs_thread_no < fThread.size()) {
		if (fThread_id) Window()->Lock();
		BTextView::ScrollTo(0, BTextView::PointAt(fThread[uabs_thread_no]).y);
		if (fThread_id) Window()->Unlock();
	}
}
////////////////////////////////////////////////////////////////////////////////
// ClearText
//
// 20051029:SetTextから名称変更。
////////////////////////////////////////////////////////////////////////////////
void
K2chTextView::ClearText()
{
	fThread.clear();
	fAction.Clear();

	BTextView::SetText("");
	this->PostStatus("");
}
////////////////////////////////////////////////////////////////////////////////
// LoadFile
//
//
////////////////////////////////////////////////////////////////////////////////
void
K2chTextView::LoadFile(BFile &file)
{
	fDat.InitCheck();
	fDat.SetTo(&file);

#ifdef DISPLAY_FROM_MIDOKU
	int32 read_count;
	file->ReadAttr(DAT_ATTR_THREADCOUNT, B_INT32_TYPE, 0, &read_count, sizeof(int32));
#endif

	this->UpdateThreadNo(&file);
	
	Base2chTextView::SetDisplayFont();
	
	// 20060729:BeOS - Zeta R5すれ@366 ：Be名無しさんより設定が反映されるように修正。  
	fThreadDisplayRange = atoi(theSettings().GetString(DISPLAY_THREAD_RANGE));
	
	this->SetDispmode((strcmp(theSettings().GetString(DISPLAY_ALL_THREAD), "1") == 0) ? DISP_ALL_THREAD : DISP_PARTIAL_THREAD);
	// fDatが定義されないとSeekは意味をなさない
	if (strcmp(theSettings().GetString(DISPLAY_FIRST_THREAD), "1") == 0)
		this->Seek(0, SEEK_SET);
	if (strcmp(theSettings().GetString(DISPLAY_LAST_THREAD), "1") == 0)
		this->Seek(-fThreadDisplayRange, SEEK_END);

#ifdef DISPLAY_FROM_MIDOKU
		this->Seek(read_count - 1, SEEK_SET);
#endif
	
	this->LoadText();
}

////////////////////////////////////////////////////////////////////////////////
// StopLoad
//
//
////////////////////////////////////////////////////////////////////////////////
void
K2chTextView::StopLoad()
{
	fCancel = true;
	this->PostStatus("Window Unlock. thread 終了待ち");
	Window()->Unlock();
	status_t exitthread;
	wait_for_thread(fThread_id, &exitthread);	// for Cancel
	Window()->Lock();
	this->PostStatus("Window Locked");
}
////////////////////////////////////////////////////////////////////////////////
// LoadText
//
//
////////////////////////////////////////////////////////////////////////////////
void
K2chTextView::LoadText(int32 range, int32 scroll_thread_no)
{
	fRange = (range == 0) ? fThreadDisplayRange : range;
	fScrollThreadNo = scroll_thread_no;

	if (fThread_id) {
		this->StopLoad();
	}

	this->ClearText();

	this->PostStatus("Loading.. .");
	fThread_id = spawn_thread(InsertMonaRun, "InsertMona", B_NORMAL_PRIORITY, this);
	resume_thread(fThread_id);
}
////////////////////////////////////////////////////////////////////////////////
// AcceptReload
//
//
////////////////////////////////////////////////////////////////////////////////
// TODO: 全表示と最新nすれ表示痔のみReloadを許可。
bool
K2chTextView::AcceptReload()
{
	return true;
}
////////////////////////////////////////////////////////////////////////////////
// seek
//
//
////////////////////////////////////////////////////////////////////////////////
void
K2chTextView::Seek(int32 offset, uint32 seek_mode)
{
	if (seek_mode == SEEK_SET){
		fCurPositon = 0;
	}else if(seek_mode == SEEK_END){
		fCurPositon = fDat.CountItems();
	}
	
	fCurPositon += offset;
	if(fCurPositon < 0) fCurPositon = 0;
	if(fCurPositon > fDat.CountItems()) fCurPositon = fDat.CountItems();
}
////////////////////////////////////////////////////////////////////////////////
// SetDispmode
//
//
////////////////////////////////////////////////////////////////////////////////
void
K2chTextView::SetDispmode(disp_mode mode)
{
	fDispmode = mode;
}
////////////////////////////////////////////////////////////////////////////////
// Dispmode
//
//
////////////////////////////////////////////////////////////////////////////////
disp_mode
K2chTextView::Dispmode()
{
	return fDispmode;
}

////////////////////////////////////////////////////////////////////////////////
// InsertMonaRun
//
//
////////////////////////////////////////////////////////////////////////////////
int32
K2chTextView::InsertMonaRun(void *data)
{
	K2chTextView* self = static_cast<K2chTextView*>(data);
	self->fCancel = false;
	self->InsertMona();
	if (!self->fCancel){
		BMessenger(self->Window()).SendMessage(kMSG_THREAD_END, self);
	}
	self->fThread_id = 0;
	return B_OK;
}
////////////////////////////////////////////////////////////////////////////////
// InsertMona
//
//
////////////////////////////////////////////////////////////////////////////////
void
K2chTextView::InsertMona()
{
	int32 start = fCurPositon;
	int32 last = (Dispmode() == DISP_ALL_THREAD ? fDat.CountItems():fCurPositon + fRange);

	if( last > fDat.CountItems()){
		last = fDat.CountItems();
	}
	if(start == 0 && last == fDat.CountItems()){
		this->SetDispmode(DISP_ALL_THREAD);
	}

	PRINT(("pos=%ld count=%ld mode=%ld\n", start, last, Dispmode()));
	Window()->Lock();
	MakeSelectable(false);
	fScrollView->Hide();
	Window()->Unlock();

	for (int32 i = start; i < last && !fCancel; i++) {
		fThread.push_back(BTextView::TextLength());
		this->InsertThread(i, this);
		snooze(10);
		char msg[32];
		sprintf(msg, "Insert %ld", i + 1);
		if (fThread_id) Window()->Lock();
		this->PostStatus(msg);
		if (fThread_id) Window()->Unlock();
	}
#ifdef ALWAYS_LAST_SHOW
	if (fScrollThreadNo == 0 && last == fDat.CountItems())
		fScrollThreadNo = last;
#endif
	if (fScrollThreadNo == 0)
		fScrollThreadNo = fCurPositon + 1;
	this->ScrollToRes(fScrollThreadNo);

	Window()->Lock();
	fScrollView->Show();
	MakeSelectable(true);
	Window()->Unlock();
}
////////////////////////////////////////////////////////////////////////////////
// InsertThread
//
//
////////////////////////////////////////////////////////////////////////////////
void
K2chTextView::InsertThread(int32 i, Base2chTextView *outView)
{
	int32 argc;
	const char *argv[6];
	this->fDat.OffsetAt(i, &argc, argv);
	//20060125:エラー対策
	if (!argv[1]) return;
	
	if (fThread_id) Window()->Lock();
	outView->DiaplayThread(i + 1, argv);
	if (fThread_id) Window()->Unlock();
}

////////////////////////////////////////////////////////////////////////////////
// Invoke
//
//
////////////////////////////////////////////////////////////////////////////////
void
K2chTextView::Invoke()
{
	const action_info* action = fAction.Current();
	if (action && action->name) {
		if (strncmp(action->name, "http://", 7) == 0){
			Launcher(B_URL_HTTP, action->name);
		}else if (strncmp(action->name, "../", 3) == 0) {
			this->InvokeThread(atoi(strrchr(action->name, '/') + 1));
		}
	}
}
////////////////////////////////////////////////////////////////////////////////
// InvokeThread
//
//
////////////////////////////////////////////////////////////////////////////////
void
K2chTextView::InvokeThread(int32 thread_no)
{
	PRINT(("InvokeThread(%ld)\n", thread_no));
	BMessage msg(kMSG_BORN_BABY);
	msg.AddInt32("thread_no", thread_no);
	BMessenger(be_app->WindowAt(0)->FindView("2ch:TextView")).SendMessage(&msg);
}
////////////////////////////////////////////////////////////////////////////////
// SelectionInvoke
// Selectionされた範囲が数字ならレス番号として起動する */
//
////////////////////////////////////////////////////////////////////////////////
void
K2chTextView::SelectionInvoke()
{
	int32 start, finish;
	BTextView::GetSelection(&start, &finish);
	if (start < finish) {
		int32 size = is_number(start );	/* Selectionされたものが数字か */
		if (size == (finish - start) && size  < 5) {
			char buf[8];
			BTextView::GetText(start, size, buf);// 20060518syuusei
			buf[size] = 0;
			this->InvokeThread(atoi(buf));
		}
	}
}
////////////////////////////////////////////////////////////////////////////////
// SetInvoke
//	現在のテキスト位置(TextLength())からlengthまでをaction範囲とする。
//
////////////////////////////////////////////////////////////////////////////////
void 
K2chTextView::SetInvoke(const char *name, int32 length)
{
	//20051123
	if (name) this->SetInvoke(name, strlen(name), length);
}

////////////////////////////////////////////////////////////////////////////////
// SetInvoke
//	現在のテキスト位置(TextLength())からlengthまでをaction範囲とし
//	action名としてnameからsize分が使われる
////////////////////////////////////////////////////////////////////////////////
void
K2chTextView::SetInvoke(const char *name, int32 size, int32 length)
{
	//20060528
	if(!name) return;
	
	action_info *action = new action_info;
	action->offset = this->TextLength();
	action->length = (length < 0) ? strlen(name) : length;
	action->size = size;
	if(size == 3 && !strncmp(name, "age", 3)){
		action->type = ACTION_AGE;
	}else if (size == 4 && !strncmp(name, "sage", 4)){
		action->type = ACTION_SAGE;
	}else{
		char *buf = static_cast<char *>(malloc(size + 1));
		strncpy(buf, name, size);
		buf[size] = '\0';
		action->name = buf;
		if(!strncmp(action->name, "../", 3)){
			action->type = ACTION_TEST;
		}else{
			action->type = ACTION_JMP_URL;
		}
	}
	fAction.AddAction(action);
}
////////////////////////////////////////////////////////////////////////////////
// ThreadSelection
//
//
////////////////////////////////////////////////////////////////////////////////
void
K2chTextView::ThreadSelection(BPoint where, int32 &start, int32 &end)
{
	start = 0;
	int32 finish = 0;
	int32 offset = BTextView::OffsetAt(where);

	for (uint32 i = 1; i < fThread.size(); i++) {
		finish = fThread[i];
		if (start <= offset && offset < finish) {
			end = finish;
			return;
		}
		start = finish;
	}
	end = BTextView::TextLength();
	start = (offset < end) ? finish : offset;
}
////////////////////////////////////////////////////////////////////////////////
// ShowTipWindow
//
// 20060320:修正
////////////////////////////////////////////////////////////////////////////////
void
K2chTextView::ShowTipWindow(int32 thread_no)
{
	if (fTipStiker){
		fTipStiker->Close();
		fTipStiker = NULL;		
	}

	BRect frame(0, 0, 2000, 2000);
//	frame.right += 6;
//	frame.bottom += 6;

//	K2chTipTextView *clone = new K2chTipTextView(frame, "", frame.InsetByCopy(3, 3), ResizingMode(), Flags());
	K2chTipTextView *clone = new K2chTipTextView("", Flags());
	this->InsertThread(thread_no - 1, clone);

	float r = 0, b = 0;
	for (int32 i = clone->LineAt(0); i < clone->LineAt(clone->TextLength()); i++) {
		b += clone->LineHeight(i);
		if (r < clone->LineWidth(i)) r = clone->LineWidth(i) + 6;
	}

	frame = BRect(0, 0, r - 1, b - 1);
	clone->ResizeTo(frame.Width(), frame.Height());
	clone->SetViewColor(255, 253, 251);

	fTipStiker = new MSticker(frame, clone);
	fTipStiker->ShowSticker(ConvertToScreen(fStatic_where));

}
////////////////////////////////////////////////////////////////////////////////
// UpdateThreadNo
//
//
////////////////////////////////////////////////////////////////////////////////
void
K2chTextView::UpdateThreadNo(BFile *file)
{
int32 count = fDat.CountItems();

	this->PostStatus("update Attribute");

	MakeFocus(true); /* 必然性なし。利便性がいいので*/
		//スレの読込みが完了したときのみレス数を更新する。
		//TODO:読込みが完了しない場合はレス数の所得はどうするか？
		//一度に全部読込むので問題なし(20060701)
	char buf[16];
	ssize_t size = file->ReadAttr(DAT_ATTR_DATNAME, B_STRING_TYPE, 0, &buf, sizeof(buf));
	buf[size] = '\0';
	BMessage m(kMSG_UPDATE_THREADCOUNT);
	m.AddString("dat", buf);
	m.AddInt32("thread_no", count);
	BMessenger(this->Window()).SendMessage(&m);
	file->WriteAttr(DAT_ATTR_THREADCOUNT, B_INT32_TYPE, 0, &count, sizeof(int32));
		
	this->PostStatus("");
}
////////////////////////////////////////////////////////////////////////////////
// AddResMenuItem
//
//
////////////////////////////////////////////////////////////////////////////////
void
K2chTextView::AddResMenuItem(BPopUpMenu *menu, const char *msg, uint32 command)
{
	BString threadMenuName;
	threadMenuName << msg << fThreadDisplayRange << "レス表示。";
	menu->AddItem(MakeItem(this, threadMenuName.String(), command)); 
}
////////////////////////////////////////////////////////////////////////////////
// AddMenuItems
//
//
////////////////////////////////////////////////////////////////////////////////
void
K2chTextView::AddMenuItems(BPopUpMenu *menu, BPoint where)
{
	int32 start, end;
	BMessage *msg;
	
	this->GetSelection(&start, &end);
	if (start < end) {
		msg = new BMessage(kMSG_GOOGLE);
		BString data;
		GetText(start, end - start, data.LockBuffer(end - start + 1));// const
		data.UnlockBuffer();
		msg->AddString("selected", data);
		menu->AddItem(new BMenuItem("Googleで検索。", msg));
		menu->AddItem(MakeItem(this, "Copy", B_COPY)); 
		menu->AddSeparatorItem();
	}

	fThreadDisplayRange = atoi(theSettings().GetString(DISPLAY_THREAD_RANGE));

	this->AddResMenuItem(menu, "最新", kMENU_THREAD_GO_LAST);
	this->AddResMenuItem(menu,  "1から", kMENU_THREAD_GO_TOP);

	//TODO:表示しないのが良いのか、表示＋disableにした方がいいのか？
	if(Dispmode() == DISP_PARTIAL_THREAD){
		if(fCurPositon > 0){ //20060806
			this->AddResMenuItem(menu,  "前", kMENU_THREAD_GO_PREV);
		}
		if(fCurPositon < fDat.CountItems() - fThreadDisplayRange){ //20060806
			this->AddResMenuItem(menu,  "次", kMENU_THREAD_GO_NEXT);
		}
	}

	menu->AddItem(MakeItem(this, "全レス表示。", kMENU_THREAD_SHOW_All)); 

	this->ThreadSelection(where, start, end);
	
	if (start < end) {
		BString data;
		BTextView::GetText(start, end - start, data.LockBuffer(end - start + 1));
		data.UnlockBuffer();
		menu->AddItem(MakeItem(this, "レスをコピー", kMSG_COPY_THREAD, data));
	}

	action_info* action = fAction.ItemTo(OffsetAt(where));
	if (action && action->name) {
		menu->AddItem(MakeItem(this, "リンクをコピー", kMSG_COPY_LOCATION, action->name));
		if(action->type != ACTION_JMP_URL)	return;
		BString uri;
		uri <<"http://so.7walker.net/?"<< action->name;
		menu->AddItem(MakeItem(this, "ソースチェッカーオンライン", kMSG_CHECK_LOCATION, uri));
	}
}
////////////////////////////////////////////////////////////////////////////////
// UpdatePreferences
//
// 
////////////////////////////////////////////////////////////////////////////////
void
K2chTextView::UpdatePreferences(BMessage *)
{
	SetDisplayFont();
	LoadText(fRange);
}
////////////////////////////////////////////////////////////////////////////////
// GoogleSearch
//
// 20060729:MUtil.h（class GoogleURL）から移動。
////////////////////////////////////////////////////////////////////////////////
void
K2chTextView::GoogleSearch(const char* searchText)
{
	BString google("http://www.google.co.jp/search?hl=ja&ie=Shift_JIS&q=");
	Urlescape(searchText, google);
	google << "&lr=lang_ja";
	Launcher(B_URL_HTTP, google.String());
}

////////////////////////////////////////////////////////////////////////////////
// MakeFocus
//
// 2009/01/11:aowakuhyoujiyou
////////////////////////////////////////////////////////////////////////////////
void
K2chTextView::MakeFocus(bool focused)
{
	if (this->IsFocus() == focused)
		return;
		
	Base2chTextView::MakeFocus(focused);

	if (fScrollView)
		fScrollView->SetBorderHighlighted(focused);
}

////////////////////////////////////////////////////////////////////////////////
// TargetedByScrollView
//
// 2009/01/11:aowakuhyoujiyou
////////////////////////////////////////////////////////////////////////////////
void
K2chTextView::TargetedByScrollView(BScrollView *view)
{
	fScrollView = view;
}
